/* 
 * revised for pylibmask. Works for g++ under cygwin or minGW under winxp with python2.7.
 *
 * Low-level routines to write the GDSII file.
 *
 * Copyright (c) 2010 Xaveer Leijtens
 *
 * This file is part of libMask.
 *
 * libMask is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * libMask is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with libMask.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "libgds.h"

#ifdef __MINGW32__
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif

#define MASKFILE stderr
#define STDERR stderr

//#define roundm2nm(x) ((int32_t)(((x)*1e9)+copysign(0.5,(x))))
int32_t roundm2nm(double x)
{
 return ((int32_t)(((x)*1e9)+copysign(0.5,(x))));
}

void gds_write_int8(const uint8_t b)
{
  if (fwrite(&b, sizeof(uint8_t), 1, MASK) != 1) {
    fprintf(stderr, "Error in GDSII write_int8\n");
    exit(1);
  }
}

void gds_write_int16(const uint16_t s)
{
  uint16_t S = htons(s);
  
  if (fwrite(&S, sizeof(uint16_t), 1, MASK) != 1) {
    fprintf(stderr, "Error in GDSII write_int16\n");
    exit(1);
  }
}

void gds_write_int32(const uint32_t i)
{  
  uint32_t I = htonl(i);
  
  if (fwrite(&I, sizeof(uint32_t), 1, MASK) != 1) {
    fprintf(stderr, "Error in GDSII write_int32\n");
    exit(1);
  }
}

void gds_write_real8(double D)
{
  uint8_t rec[8];
  memset(rec, 0, 8);
  if (D < 0) {
    rec[0] = 0x80;
    D = -D;
  }
  int E = 64;
  uint64_t M;
  if (D < 8e-78) { // 16^-64 
    rec[0] |= 64;
    
    if (fwrite(rec, sizeof(uint8_t), 8, MASK) != 8) {
      fprintf(stderr, "Error in GDSII write_double\n");
      exit(1);
    }
    return;
  }
  // Mantissa in [1/16,1> 
  if (D >= 1.0) {
    while (D >= 1.0) {
      D /= 16.0;
      E++;
    }
  } else if (D < 0.0625) {
    while (D < 0.0625) {
      D *= 16.0;
      E--;
    }
  }
  rec[0] |= (E & 0x7F);
  M = uint64_t(D * 0x100000000000000LL + 0.5);
   
  for (int i = 7; i > 0; --i) {
    rec[i] = (M & 0xFF);
    M >>= 8;
  }
  
  if (fwrite(rec, sizeof(uint8_t), 8, MASK) != 8) {
    fprintf(stderr, "Error in GDSII write_double\n");
    exit(1);
  }
}

/*
void gds_write_real8(double d)
{//copied from dbGDS2Writer.cc (KLayout Matthias Koefferlein) http://blog.peterscholtens.net/?p=281
  //fprintf(MASK, "gds_write_real8 in\n");//DEBUG
  char b[8];
  // Create pointer for 64 bit output word 
  uint64_t* ptr = reinterpret_cast<uint64_t*>(b);

  // Interpret double as an 64 bit unsigned integer 
  uint64_t d_bits   = *(reinterpret_cast<uint64_t*>(&d) );

  // Fraction takes binary content of last 52 bits, while
  //    adding implicitly removed leading '1' (IEEE754) 
  uint16_t exp_gdsii;
  uint64_t fraction = (d_bits & 0xFFFFFFFFFFFFFLL) | 0x10000000000000LL;

  // Take 2nd till 12th bit, and right shift them 52 positions,
  //    as representation of the exponent 
  uint16_t exp_ieee = uint16_t((d_bits & 0x7FF0000000000000LL) >> 52);

  // Verify if IEEE754 exponent exceeds Calma format range, return error 
  if (exp_ieee > 1275) {
      fraction  = uint64_t(0x7FFFFFFFFFFFFFFFLL);
      exp_gdsii = 0x7F;

  // Verify if exponent is too small, then round to real zero 
  } else if (exp_ieee < 767) {
      fraction  = uint64_t(0x0);
      exp_gdsii = 0x40;// Mimics original function, should be 0x00 ??
  } else {
      // Convert power of 2 to power of 16, remainder absorbed by fraction
      // as ieee-1023 is default, add 4 (changing from 52 to 56 bit size of
      // the fraction) and adding 4 times 64, the new offset of base 16.
      exp_gdsii = exp_ieee - 1023 + 4 + 4*64;
      for(int i=1; i<3;i++)
      {
          // As factors 2^1, 2^2 and 2^3 do not fit in 2^4, shift fraction
          //  accordingly, if needed. 
          if(exp_gdsii & 0x1) {
              fraction <<= i;
          }
          exp_gdsii >>= 1; // Looped twice, thus effectively exp is divided by four
      }
  }
  // Write result to memory: binary OR function of sign, exponent and fraction 
  *ptr = (uint64_t)((d_bits & 0x8000000000000000LL) | (uint64_t(exp_gdsii) << 56) | fraction );
  
  if (fwrite(b, sizeof(char), 8, MASK) != 8) {
    fprintf(stderr, "Error in GDSII write_double\n");
    exit(1);  
  }
}
*/

void gds_write_time(void)
{
  //fprintf(MASKFILE, "**WTIME**");//DEBUG
  for (int i = 0; i < 6; ++i) {
    gds_write_int8(0);
  }
}

void gds_write_string(const char *t)
{
  //fprintf(MASKFILE, "**WSTRING**");//DEBUG
  
  size_t len = strlen(t);
  
  
  //fprintf(MASKFILE, "**GDSI*STRING**");//DEBUG
  //for(int i=0; i<len; i++)fprintf(stderr, "gds_write_real8: %c, %x\n", t[i], t[i]);//DEBUG
  
  
  if (fwrite(t, sizeof(char), len, MASK) != len) {
    fprintf(stderr, "Error in GDSII write_string\n");
    exit(1);
  }
  if ((len & 1)) gds_write_int8(0);
}

/* GDS records */
void gds_header(void)
{
  //fprintf(MASKFILE, "**HEAD**");//DEBUG
  gds_write_int16(6); /* Record length = 4 + 2 */
  gds_write_int8(GDS_REC_HEADER);
  gds_write_int8(GDS_VAL_INT16);
  gds_write_int16(0x0180); /* software version */
}

void gds_bgnlib(void)
{
  //fprintf(MASKFILE, "**BGNLIB**");//DEBUG
  gds_write_int16(28); /* Record length = 4 + 2*12 */
  gds_write_int8(GDS_REC_BGNLIB);
  gds_write_int8(GDS_VAL_INT16);
  for (int i=0; i<12; i++) gds_write_int16(0); /* date, time */
}

void gds_libname(const char *name)
{
  //fprintf(MASKFILE, "**LIBNAME**");//DEBUG
  size_t len = strlen(name);
  if (len & 1) len++;
  gds_write_int16(4 + len); /* Record length = 4 + strlen */
  gds_write_int8(GDS_REC_LIBNAME);
  gds_write_int8(GDS_VAL_ASCII);
  gds_write_string(name);
}

void gds_units(void)
{ 
  //fprintf(MASKFILE, "**UNIT**");//DEBUG
  gds_write_int16(20); /* Record length = 4 + 2*8 */
  gds_write_int8(GDS_REC_UNITS);
  gds_write_int8(GDS_VAL_REAL8);
  gds_write_real8(0.001);
  gds_write_real8(1e-9);
}

void gds_nodata(const uint8_t rec)
{
  //fprintf(MASKFILE, "**NODATA**");//DEBUG
  gds_write_int16(4); /* Record length = 4 */
  gds_write_int8(rec);
  gds_write_int8(GDS_VAL_NODATA);
}

void gds_bgnstr(void)
{
  //fprintf(MASKFILE, "**BGNSTR**");//DEBUG
  gds_write_int16(28); /* Record length = 4 + 2*12 */
  gds_write_int8(GDS_REC_BGNSTR);
  gds_write_int8(GDS_VAL_INT16);
  for (int i=0; i<12; i++) gds_write_int16(0); /* date, time */
}

void gds_strname(const char *name)
{
  //fprintf(MASKFILE, "**STRNAME**");//DEBUG
  size_t len = strlen(name);
  if (len & 1) len++;
  gds_write_int16(4 + len); /* Record length = 4 + strlen */
  gds_write_int8(GDS_REC_STRNAME);
  gds_write_int8(GDS_VAL_ASCII);
  gds_write_string(name);
}

void gds_layer(const uint16_t lay)
{
  //fprintf(MASKFILE, "**LAYER**");//DEBUG
  gds_write_int16(6); /* Record length = 4 + 2 */
  gds_write_int8(GDS_REC_LAYER);
  gds_write_int8(GDS_VAL_INT16);
  gds_write_int16(lay);
}

void gds_width(const double w)
{
  //fprintf(MASKFILE, "**SNAME**");//DEBUG
  gds_write_int16(8); /* Record length = 4 + 4 */
  gds_write_int8(GDS_REC_WIDTH);
  gds_write_int8(GDS_VAL_INT32);
  gds_write_int32(roundm2nm(w));
}

void gds_xy(size_t n, double *x, double *y, int close)
{
  //fprintf(MASKFILE, "***XY***");//DEBUG
  if (close) {
    gds_write_int16(4 + 8 * (n+1));
  } else {
    gds_write_int16(4 + 8 * n); /* Record length = 4 + 4*2*n */
  }
  gds_write_int8(GDS_REC_XY);
  gds_write_int8(GDS_VAL_INT32);
  for (size_t i=0; i<n; i++) {
    gds_write_int32(roundm2nm(x[i]));
    gds_write_int32(roundm2nm(y[i]));
  }
  if (close) {
    gds_write_int32(roundm2nm(x[0]));
    gds_write_int32(roundm2nm(y[0]));
  }
}

void gds_sname(const char *name)
{
  //fprintf(MASKFILE, "**SNAME**");//DEBUG
  size_t len = strlen(name);
  if (len & 1) len++;
  gds_write_int16(4 + len); /* Record length = 4 + strlen */
  gds_write_int8(GDS_REC_SNAME);
  gds_write_int8(GDS_VAL_ASCII);
  gds_write_string(name);
}

void gds_strans(const uint16_t bits)
{
  //fprintf(MASKFILE, "**STRANS**");//DEBUG
  gds_write_int16(6); /* Record length = 4 + 2 */
  gds_write_int8(GDS_REC_STRANS);
  gds_write_int8(GDS_VAL_BITARRAY);
  gds_write_int16(bits);
}

void gds_mag(const double mag)
{
  //fprintf(MASKFILE, "**MAG**");//DEBUG
  gds_write_int16(12); /* Record length = 4 + 8 */
  gds_write_int8(GDS_REC_MAG);
  gds_write_int8(GDS_VAL_REAL8);
  gds_write_real8(mag);
}

void gds_angle(const double ang)
{
  //fprintf(MASKFILE, "**ANGLE**");//DEBUG
  gds_write_int16(12); /* Record length = 4 + 8 */
  gds_write_int8(GDS_REC_ANGLE);
  gds_write_int8(GDS_VAL_REAL8);
  gds_write_real8(ang);
}

void gds_datatype(int16_t type)
{
  //fprintf(MASKFILE, "**DATATYPE**");//DEBUG
  gds_write_int16(6); /* Record length = 4 + 2 */
  gds_write_int8(GDS_REC_DATATYPE);
  gds_write_int8(GDS_VAL_INT16);
  gds_write_int16(type);
}

void gds_pathtype(int16_t type)
{
  //fprintf(MASKFILE, "**PATHTYPE**");//DEBUG
  gds_write_int16(6); /* Record length = 4 + 2 */
  gds_write_int8(GDS_REC_PATHTYPE);
  gds_write_int8(GDS_VAL_INT16);
  gds_write_int16(type);
}

void gds_polyline(const size_t n, double *x, double *y, double w)
{
  //fprintf(MASKFILE, "**POLYLINE**");//DEBUG
  gds_nodata(GDS_REC_PATH);
  gds_layer(clayer);
  gds_datatype(0);
  gds_pathtype(0);
  gds_width(w); 
  gds_xy(n, x, y, false);
  gds_nodata(GDS_REC_ENDEL);
}

void gds_polygon(const size_t n, double *x, double *y)
{
  //fprintf(MASKFILE, "**POLYGON**");//DEBUG
  gds_nodata(GDS_REC_BOUNDARY);
  gds_layer(clayer);
  gds_datatype(0);
  gds_xy(n, x, y, true);
  gds_nodata(GDS_REC_ENDEL);
}
